﻿using System;
using System.Collections.Generic;
using System.Text;
using Ord;

namespace OrdonnanceAI
{
    // AInum = 2
    static class BotClever
    {
        static Random rnd = new Random((int)DateTime.Now.Ticks);

        // подходимость карты к городу.
        // не подходит, подходит но с меньшим количеством линий, подходит по всем линиям
        // notFit - не значит, что карта совсем не подходит. Она может подходить, но уступив при этом, например линию, когда у противника окажется 2 линии, а у города одна
        enum CardFit { NotFit, Fit, FitMoreStrong, H2, L2, H1, L1 }

        // ТИПЫ ГОРОДОВ:
        // полностью пустой - если он еще не заявлен ни одним игроком
        // пустой (только противник) - город, заполнен хртя бы одним из противников, но не заявлен ботом
        // заполнен - город у бота заполнен
        // явно-сливной - город либо заведомо выигрышный, либо заведомо проигрышный
        // конфликтный - город, за который можно бороться. Это город, заполненный ботом и хотя бы одним противником
        // только игрок - город заполняемый только игроком
        enum TownType { AllEmpty, BotEmptyClmEnemy, BotEmptyClmEnemyALL, FullTown, DropTown, FightTown, OnlyBot }

        enum RYGStat { None, Only_RedYellow, Only_RedGreen, RedYellow_And_RedGreen }

        enum CollectCompareResult { Collect1_MoreStrong, Collect1_Fit, Collect2_MoreStrong, Collect2_Fit, Equal }

        #region *** ФИИ ****************************************************

        #region *** Подсчет веса карты **************************************

        /// <summary> Индекс карты с минимальным весом из всех карт</summary>
        /// <param name="cards">список карт</param>
        /// <param name="onlyInd">список индексов карт, которые должны считаться (если null, считаются все карты)</param>
        static int GetMinWeightInd(List<Card> cards, List<int> onlyInd, out int MinWeight)
        {
            return GetMinMaxWeightInd(cards, onlyInd, out MinWeight, true);
        }

        /// <summary> Индекс карты с минимальным весом из всех карт</summary>
        /// <param name="cards">список карт</param>
        /// <param name="onlyInd">список индексов карт, которые должны считаться (если null, считаются все карты)</param>
        static int GetMinWeightInd(List<Card> cards, List<int> onlyInd)
        {
            int MштWeight;
            int ret = GetMinMaxWeightInd(cards, onlyInd, out MштWeight, true);
            return ret;
        }

        /// <summary> Индекс карты с максимальным весом из всех карт</summary>
        /// <param name="cards">список карт</param>
        /// <param name="onlyInd">список индексов карт, которые должны считаться (если null, считаются все карты)</param>
        static int GetMaxWeightInd(List<Card> cards, List<int> onlyInd, out int MaxWeight)
        {
            return GetMinMaxWeightInd(cards, onlyInd, out MaxWeight, false);
        }

        /// <summary> Индекс карты с максимальным весом из всех карт</summary>
        /// <param name="cards">список карт</param>
        /// <param name="onlyInd">список индексов карт, которые должны считаться (если null, считаются все карты)</param>
        static int GetMaxWeightInd(List<Card> cards, List<int> onlyInd)
        {
            int MaxWeight;
            int ret = GetMinMaxWeightInd(cards, onlyInd, out MaxWeight, false);
            return ret;
        }

        static int GetMinMaxWeightInd(List<Card> cards, List<int> onlyInd, out int Weight, bool bMin)
        {
            Weight = bMin ? int.MaxValue : int.MinValue;
            int retInd = -1;
            for (int c = 0; c < cards.Count; c++)
            {
                if (onlyInd != null && !onlyInd.Contains(c)) continue;

                int weight = Card.GetWeight(cards[c].troop);
                if ((bMin && Weight > weight) || (!bMin && Weight < weight))
                {
                    Weight = weight;
                    retInd = c;
                }
            }
            return retInd;
        }

        #endregion *** Подсчет веса карты **************************************

        /// <summary>номер самой потенциально-сильной карты</summary>
        /* static int GetMaxCardInd(List<Card> card)
         {
             // потенциально-сильная карта:
             // это карта, накапливающая самые старшие 2 линии
             // из таких карт выбирается самая сильная по весу

             // оставляем у всех карт только 2 самых старших бойца (или одного, если он единственный)
             // и по этому остатку вычисляем старшую 
             List<bool> crt
            
             int max = -1;
             int ret = -1;
             for (int i = 0; i < card.Count; i++)
             {
                 if (max < wgt[i])
                 {
                     max = wgt[i];
                     ret = i;
                 }
             }
             return ret;
         }*/

        /// <summary>Город уже проигран</summary>
        static bool TownAlreadyLost(List<Town[]> EnemyTowns, int townInd, Town town)
        {
            Town myTown = town.Clone();
            // заполняем проверяемый город самыми сильными картами
            while (myTown.card.Count < 4) myTown.card.Add(new Card(true, true, true, true, true));

            // город заведомо потерян, если при дополнении его самыми сильными картами, а городов противника полностью нулевыми картами, город остается проинрышным
            // пробегаемся по городам противника
            for (int i = 0; i < EnemyTowns.Count; i++)
            {
                Town enTown = EnemyTowns[i][townInd].Clone();
                // если город противника пуст, переходим к следующему
                if (enTown.card.Count == 0) continue;

                // дополняем вражеский город нулевыми картами
                while (enTown.card.Count < 4) enTown.card.Add(new Card(false, false, false, false, false));

                // если проверяемый город все равно проигран, возвращаем true
                if (myTown.Leader(enTown) == Town.LeaderStat.Lose) return true;
            }

            return false;
        }

        /// <summary>Город уже выигран</summary>
        static bool TownAlreadyWin(List<Town[]> EnemyTowns, int townInd, Town town)
        {
            Town myTown = town.Clone();
            // заполняем проверяемый город нулевыми картами
            while (myTown.card.Count < 4) myTown.card.Add(new Card(false, false, false, false, false));

            // город заведомо выигрышный, если при дополнении его нулевыми картами, а городов противника самыми сильными картами, город остается выигрышным
            // пробегаемся по городам противника
            for (int i = 0; i < EnemyTowns.Count; i++)
            {
                Town enTown = EnemyTowns[i][townInd].Clone();
                // если город противника пуст, возвращаем false. Так как еще неизвестно, что туда выложит противник
                if (enTown.card.Count == 0) return false;

                // дополняем вражеский город нулевыми картами
                while (enTown.card.Count < 4) enTown.card.Add(new Card(true, true, true, true, true));

                // если проверяемый город не выигрышен, возвращаем false
                if (myTown.Leader(enTown) != Town.LeaderStat.Win) return false;
            }

            // возвращаем true так как все города противника проиграли
            return true;
        }

        /// <summary>Возвращает подходимость карты для конфликтного города</summary>
        static CardFit TownFit(List<Town[]> EnemyTowns, int townInd, Town Town, Card newCardInTown)
        {
            Town townWithCard = Town.Clone();
            townWithCard.card.Add(newCardInTown);

            // сравниваем 4 (если они есть)
            // если линии 4 нет сравниваем линии 3
            // и т.д.

            // пробегаемся по разщмерам линий, начиная с полных (4)
            for (int lineSz = 4; lineSz >= 1; lineSz--)
            {
                bool[] clt = townWithCard.GetCollectedTroops(lineSz);
                bool[] enClt = GetMaxEnemyCollect(EnemyTowns, townInd, lineSz);


                if (Card.GetWeight(clt) > 0 || Card.GetWeight(enClt) > 0)
                {
                    CollectCompareResult res = CollectCompare(clt, enClt);

                    // если у игрока карта лучше. Если у игрока заведомо-сильнее, то MoreStrong, иначе просто подходит
                    if (res == CollectCompareResult.Collect1_MoreStrong) return CardFit.FitMoreStrong;
                    if (res == CollectCompareResult.Collect1_Fit) return CardFit.Fit;
                    // у противника лучше. Если у противника заведомо-сильнее, карта не подходит, иначе подходит
                    if (res == CollectCompareResult.Collect2_MoreStrong)
                    {
                        // если у бота будет копиться хотя бы 1 линия (если нет, значит карта без вариантов не подходит)
                        // если карта не подходит, смотрим, может ли карта подойти с потерями (H2 L2 H1 L1)
                        // если карта и тут не модходит, значит не подходим вовсе
                        // **********************************************************

                        // проверка на копление хотя бы 1-й линии
                        if (Card.GetWeight(clt) == 0) return CardFit.NotFit;

                        // возвращаем подходимость без учета карт противника
                        return GetFitStatusWithoutEnemy(Town, newCardInTown, lineSz);
                    }

                    if (res == CollectCompareResult.Collect2_Fit) return CardFit.Fit;

                    // если линии полностью равны, значит подходит
                    return CardFit.Fit;
                }

            }


            // если сравнение так ничего и не показало, значит где-то обшибка
            throw new Exception("Ошибка TownFit. Не удалось найти подходимость");
        }

        /// <summary>Какая карта всех лучше подходит к выкладыванию в пустой город, который заявлен ВСЕМИ противниками. 
        /// возвратит -1, если никакая не подходит. И возвращает MoreStrong. true - карта заведомо сильнее, false - карта не слабже </summary>
        static int GetFitCardInNewTownByEnemies(List<Town[]> EnemyTowns, int townInd, List<Card> cards, out bool MoreStrong)
        {
            // карты можно вычислить, только если город заявлен всеми противниками. Если нет, то и смысла нет.
            // Тут будет работать такое же правило как для выкладывания мегасильных карт

            // карта подходит в случае
            // если противник копит линии4, то нужно выложить карту с первыми двумя линиями не слабже, чем у противника
            // из таких выбирается карта заведомо сильнее, но при этом слабейшая из заведомо сильных
            // если заведомо сильнее карт нет, то выбирается самая сильная из выбранных

            // если противник не копит линии4, более подходит самая слабая карта

            //******************************************************************************

            // смотрим линии4 у каждого из противников и выбираем самое сильное
            bool[] maxClt4 = GetMaxEnemyCollect(EnemyTowns, townInd, 4);

            // если линий4 нет, выбираем самую слабую карту
            if (Card.GetWeight(maxClt4) == 0)
            {
                MoreStrong = true;
                return GetMinWeightInd(cards, null);
            }

            // линии4 есть. Смотрим карты, заведомо-сильнее и не менее слабее для накапливаемых линий.
            List<int> MoreStrongCardsInd = new List<int>();
            List<int> FitCardsInd = new List<int>();
            for (int c = 0; c < cards.Count; c++)
            {
                CardFit cf = CollectFit(cards[c].troop, maxClt4);

                if (cf == CardFit.FitMoreStrong) MoreStrongCardsInd.Add(c);
                if (cf == CardFit.Fit) FitCardsInd.Add(c);
            }

            // смотрим из заведомо-сильее, выбирая самую слабую из них
            if (MoreStrongCardsInd.Count > 0)
            {
                MoreStrong = true;
                return GetMinWeightInd(cards, MoreStrongCardsInd);
            }

            // смотрим из не слабее, выбирая самую сильную из них
            if (FitCardsInd.Count > 0)
            {
                MoreStrong = false;
                return GetMaxWeightInd(cards, FitCardsInd);
            }

            // подходящих карт нет
            MoreStrong = false;
            return -1;
        }

        static bool[] GetMaxEnemyCollect(List<Town[]> EnemyTowns, int townInd, int LineSize)
        {
            bool[] maxClt = new bool[5] { false, false, false, false, false };

            // пробегаемся по всем противникам
            for (int pl = 0; pl < EnemyTowns.Count; pl++)
            {
                // пропускаем врагов с пустым городом
                if (EnemyTowns[pl][townInd].card.Count == 0) continue;

                bool[] clt = EnemyTowns[pl][townInd].GetCollectedTroops(LineSize);

                // сравниваем
                CollectCompareResult res = CollectCompare(maxClt, clt);
                if (res == CollectCompareResult.Collect2_MoreStrong || res == CollectCompareResult.Collect2_Fit) maxClt = clt;

            }
            return maxClt;
        }

        /// <summary>Сравнивает 2 накопления методом CollectFit. Если накопления Fit, то сравнивается по весу
        /// MoreStrong - карта явно копит сильнее. Fit - карта копит то же по лидирующим 1-й или 2-м линиям, и сильнее по остальным линиям</summary>
        static CollectCompareResult CollectCompare(bool[] Collect1, bool[] Collect2)
        {
            int weight1 = Card.GetWeight(Collect1);
            int weight2 = Card.GetWeight(Collect2);
            // если веса равны, говорим, что равны
            if (weight1 == weight2) return CollectCompareResult.Equal;

            // сравниваем 
            CardFit cf = CollectFit(Collect1, Collect2);

            if (cf == CardFit.FitMoreStrong) return CollectCompareResult.Collect1_MoreStrong;  // если 1 однозначно лучше
            if (cf == CardFit.NotFit) return CollectCompareResult.Collect2_MoreStrong;         // если 2 однозначно лучше

            // если линии примерно равны, 
            // сравниваем по весу
            if (weight1 > weight2) return CollectCompareResult.Collect1_Fit;
            return CollectCompareResult.Collect2_Fit;
        }

        /// <summary>Копление заведомо сильнее
        ///   ЗАВЕДОМО-СИЛЬНЕЕ - это карты, которые копят не меньше линий (но двух достаточно) и при этом:
        /// - либо у бота старшая линия сильнее
        /// - либо, у бота старшая линия такая же, но сильнее следующая линия. 
        ///   НЕ МЕНЕЕ СЛАБЫЕ - это карты, которые могут копить все те же самые линии (но двух достаточно) и при этом
        /// - либо у бота старшая линия не слабее
        /// - либо, у бота старшая линия такая же, но не слабее следующая линия.</summary>
        static CardFit CollectFit(bool[] CollectBot, bool[] CollectEnemy)
        {
            List<int> MoreStrongCardsInd = new List<int>();
            List<int> FitCardsInd = new List<int>();

            // смотрим, количество линий
            int weight = Card.GetWeight(CollectBot);
            int enWeight = Card.GetWeight(CollectEnemy);

            int botLnSize = weight / 100;
            int enmLnSize = enWeight / 100;

            // если у бота линий меньше и меньше 2-х, карта слабее
            if (botLnSize < 2 && botLnSize < enmLnSize) return CardFit.NotFit;

            // если у противника линий меньше и меньше 2-х, карта явно-сильнее
            if (enmLnSize < 2 && enmLnSize < botLnSize) return CardFit.FitMoreStrong;

            // если линий ни там ни там нет, говорим, что подходит
            if (weight == 0 && enWeight == 0) return CardFit.Fit;

            // смотрим, заведомо-сильнее и не менее слабые
            bool bFit = true; // карта не слабее
            bool bMoreStrong = false; // карта заведомо-сильнее
            for (int tr = 4; tr >= 0; tr--)
            {
                // если у бота есть старшая, а у противника нет, карта заведомо сильнее
                if (CollectBot[tr] && !CollectEnemy[tr]) { bMoreStrong = true; break; }
                // если у противника есть старшая, а у бота нет, карта проигрышная
                if (!CollectBot[tr] && CollectEnemy[tr]) { bFit = false; break; }

                // если старшие совпадают
                if (CollectBot[tr] && CollectEnemy[tr])
                {
                    // смотрим следующие за старшими
                    for (int tr1 = (tr - 1); tr1 >= 0; tr1--)
                    {
                        // если у бота есть старшая, а у противника нет, карта заведомо сильнее
                        if (CollectBot[tr1] && !CollectEnemy[tr1]) { bMoreStrong = true; break; }
                        // если у противника есть старшая, а у бота нет, карта проигрышная
                        if (!CollectBot[tr1] && CollectEnemy[tr1]) { bFit = false; break; }

                        // если совпадение, карта не слабее
                        if (CollectBot[tr1] && CollectEnemy[tr1]) { bFit = true; break; }
                    }
                    break;
                }
            }

            if (bMoreStrong) return CardFit.FitMoreStrong;
            else if (bFit) return CardFit.Fit;

            return CardFit.NotFit;

        }

        /// <summary>Какая карта всех лучше подходит к выкладыванию в город, который заявлен только ботом. 
        /// возвратит -1, если никакая не подходит</summary>
        static int GetFitCardInOnlyBotTown(Town town, List<Card> cards, out CardFit fitStat)
        {

            // из таких карт выбираются карты, которые копят максимальное количество линий
            // из таких карт выбирается самая слабая

            //*****************************************************************

            // будем последовательно смотреть накапливаемые линии (сначала линии4. Если таких нет, то смотрим линии3, если таких нет, то линии2. Линии 1 не смотрим, поскольку это вообще не подходящие карты) 
            for (int LN = 4; LN >= 2; LN--)
            {
                // смотрим линии у города
                bool[] clt = town.GetCollectedTroops(LN);
                int cltWeightBeforeAddCard = Card.GetWeight(clt);
                // не копится ни одно линии размером LN
                if (cltWeightBeforeAddCard == 0) continue;

                // старшая линия
                int HCard = 0;
                for (int tr = 4; tr >= 0; tr--) if (clt[tr]) { HCard = tr; break; }

                // смотрим накапливаемые линии, для каждой карты
                List<int> H2 = new List<int>();
                List<int> L2 = new List<int>();
                List<int> H1 = new List<int>();
                List<int> L1 = new List<int>();

                int[] majorTr1 = new int[cards.Count]; // самые сильные линии из получившихся для каждой карты
                int[] majorTr2 = new int[cards.Count]; // самые сильные линии из получившихся после первых самых сильных (2-е место по силе) для каждой карты
                int[] kolVoLines = new int[cards.Count];

                // распределение карт по спискам
                for (int c = 0; c < cards.Count; c++)
                {
                    // подходимость карты без учета противников
                    CardFit cf = GetFitStatusWithoutEnemy(town, cards[c], LN, out majorTr1[c], out majorTr2[c], out kolVoLines[c]);

                    // если количество накапливаемых линий после выкладывания карты = 0, значит карта не подходит ни к одной группе
                    if (kolVoLines[c] == 0) continue;

                    switch (cf)
                    {
                        case CardFit.H2: H2.Add(c); break;
                        case CardFit.L2: H1.Add(c); break;
                        case CardFit.H1: L2.Add(c); break;
                        case CardFit.L1: L1.Add(c); break;
                    }

                }

                // если ни однго списка не получилось, значит к лидирующим линиям не подходит ни одна карта вообще 
                if (H2.Count == 0 && H1.Count == 0 && L2.Count == 0 && L1.Count == 0)
                {
                    fitStat = CardFit.NotFit;
                    return -1;
                }

                // смотрим по ниспадающей. сначала H2, потом L2 потом H1 потом L1
                if (H2.Count > 0)
                {
                    // Из таких карт выбираются карты, где вторая линия самая сильная из всех. 
                    int max = -1;
                    for (int i = 0; i < H2.Count; i++) if (max < majorTr2[H2[i]]) max = majorTr2[H2[i]];
                    // оставляем в списке только карты с максимальным вторым
                    for (int i = 0; i < H2.Count; i++) if (majorTr2[H2[i]] != max) { H2.RemoveAt(i); i--; }

                    // Из таких карт выбираются карты с максимальным количеством совпадающих линий. 
                    max = -1;
                    for (int i = 0; i < H2.Count; i++) if (max < kolVoLines[H2[i]]) max = kolVoLines[H2[i]];
                    // оставляем в списке только карты с максимальным количеством совпадающих линий
                    for (int i = 0; i < H2.Count; i++) if (kolVoLines[H2[i]] != max) { H2.RemoveAt(i); i--; }

                    // проверка на ошибки
                    if (H2.Count == 0) throw new Exception("Ошибка определения карты для города, заявленного только ботом: H2 пусто");

                    // Из таких выбирается самая слабая
                    fitStat = CardFit.H2;
                    return GetMinWeightInd(cards, H2);

                }
                else if (L2.Count > 0)
                {
                    // Из таких выбираются карты с самой сильной старшей линией.
                    int max = -1;
                    for (int i = 0; i < L2.Count; i++) if (max < majorTr1[L2[i]]) max = majorTr1[L2[i]];
                    // оставляем в списке только карты с максимальным вторым
                    for (int i = 0; i < L2.Count; i++) if (majorTr1[L2[i]] != max) { L2.RemoveAt(i); i--; }

                    // Из таких карт выбираются карты, где вторая линия самая сильня из всех.
                    max = -1;
                    for (int i = 0; i < L2.Count; i++) if (max < majorTr2[L2[i]]) max = majorTr2[L2[i]];
                    // оставляем в списке только карты с максимальным вторым
                    for (int i = 0; i < L2.Count; i++) if (majorTr2[L2[i]] != max) { L2.RemoveAt(i); i--; }

                    // Из таких карт выбираются карты с максимальным количеством совпадающих линий. 
                    max = -1;
                    for (int i = 0; i < L2.Count; i++) if (max < kolVoLines[L2[i]]) max = kolVoLines[L2[i]];
                    // оставляем в списке только карты с максимальным количеством совпадающих линий
                    for (int i = 0; i < L2.Count; i++) if (kolVoLines[L2[i]] != max) { L2.RemoveAt(i); i--; }

                    // проверка на ошибки
                    if (L2.Count == 0) throw new Exception("Ошибка определения карты для города, заявленного только ботом: L2 пусто");

                    // Из таких выбирается самая слабая
                    fitStat = CardFit.L2;
                    return GetMinWeightInd(cards, L2);
                }
                else if (H1.Count > 0)
                {
                    // Из таких карт выбирается самая слабая
                    // Из таких выбирается самая слабая
                    fitStat = CardFit.H1;
                    return GetMinWeightInd(cards, H1);
                }
                else
                {
                    // Из таких карт выбираются те, у которых совпадающая линия самая сильная. 
                    int max = -1;
                    for (int i = 0; i < L1.Count; i++) if (max < majorTr1[L1[i]]) max = majorTr1[L1[i]];
                    // оставляем в списке только карты с максимальным вторым
                    for (int i = 0; i < L1.Count; i++) if (majorTr1[L1[i]] != max) { L1.RemoveAt(i); i--; }

                    // проверка на ошибки
                    if (L1.Count == 0) throw new Exception("Ошибка определения карты для города, заявленного только ботом: L1 пусто");

                    // Из таких выбирается самая слабая
                    fitStat = CardFit.L1;
                    return GetMinWeightInd(cards, L1);
                }

            }

            // совпадение нет
            fitStat = CardFit.NotFit;
            return -1;
        }

        /// <summary>Возвращает статус подходимости без учета карт противника (H2 L2 H1 L1)</summary>
        static CardFit GetFitStatusWithoutEnemy(Town town, Card card, int LineSize)
        {
            int majorTr1, majorTr2, kolVoLines;
            CardFit ret = GetFitStatusWithoutEnemy(town, card, LineSize, out majorTr1, out majorTr2, out kolVoLines);
            return ret;
        }

        /// <summary>Возвращает статус подходимости без учета карт противника (H2 L2 H1 L1)</summary>
        static CardFit GetFitStatusWithoutEnemy(Town town, Card card, int LineSize, out int majorTr1, out int majorTr2, out int kolVoLines)
        {
            // подходимость карты:
            // Накапливаемые линии  U _ U U U
            // вариант 1            _ _ U U U
            // вариант 2            U _ U _ U
            // вариант 3            _ _ _ U U
            // вариант 4            _ _ U _ U
            // вариант 5            _ _ U U _
            // вариант 6            _ _ _ _ U
            // вариант 7            _ _ U _ _
            // вариант 8            U _ _ _ _
            // ------------------------------------
            // 1,2,3,4  (H2) - совпадают 2 и более линии, где старшая линия, это самая старшая из накапливаемых. Из таких карт выбираются карты, где вторая линия самая сильная из всех. Из таких карт выбираются карты с максимальным количеством совпадающих линий. Из таких выбирается самая слабая (будет выбрана карта 1)
            //                      ИЛИ копится только одна линия и она совпадает. Из таких карт выбирается самая слабая
            // 5        (L2) - совпдают 2 и более линии, где старшая линия, это НЕ самая старшая из накапливаемых. Из таких выбираются карты с самой сильной старшей линией. Из таких карт выбираются карты, где вторая линия самая сильня из всех. Из таких карт выбираются карты с максимальным количеством совпадающих линий. Из таких выбирается самая слабая
            // 6        (H1) - совпадает только одна линия, но она самая старшая. Из таких карт выбирается самая слабая
            // 7,8      (L1) - совпадает только одна линия, но она НЕ самая старшая. Из таких карт выбираются те, у которых совпадающая линия самая старшая. Из таких выбирается самая слабая (будет выбрана карта 7)
            // *******************************************************************************************************************

            majorTr1 = 0; // самая сильная линия из получившихся
            majorTr2 = 0; // самая сильная линия из получившихся после первой самой сильной (2-е место по силе)
            kolVoLines = 0;

            // смотрим линии у города
            bool[] clt = town.GetCollectedTroops(LineSize);
            int cltWeightBeforeAddCard = Card.GetWeight(clt);
            // не копится ни одна линия размером LN
            if (cltWeightBeforeAddCard == 0) return CardFit.NotFit;

            // старшая линия
            int HCard = 0;
            for (int tr = 4; tr >= 0; tr--) if (clt[tr]) { HCard = tr; break; }

            Town townWithCard = town.Clone();
            townWithCard.card.Add(card);

            bool[] cltWithCard = townWithCard.GetCollectedTroops(4);
            kolVoLines = Card.GetWeight(cltWithCard) / 100;

            // если количество накапливаемых линий после выкладывания карты = 0, значит карта не подходит ни к одной группе
            if (kolVoLines == 0) return CardFit.NotFit;

            // старшая линия из получившихся
            for (int tr = 4; tr >= 0; tr--) if (cltWithCard[tr]) { majorTr1 = tr; break; }

            // если 2 или более линии
            if (kolVoLines >= 2)
            {
                // вторая по старшенству линия из получившихся
                for (int tr = (majorTr1 - 1); tr >= 0; tr--) if (cltWithCard[tr]) { majorTr2 = tr; break; }

                // если самая сильная линия = самой сильной линии до выкладывания карты
                if (majorTr1 == HCard) return CardFit.H2;
                // если самая сильная линия < самой сильной линии до выкладывания карты
                else return CardFit.L2;
            }
            // если одна линия
            else
            {
                // если самая сильная линия = самой сильной линии до выкладывания карты
                if (majorTr1 == HCard)
                {
                    // если это единственная накапливаемая линия, добавляем карту в H2
                    if (cltWeightBeforeAddCard < 200) return CardFit.H2;
                    //иначе, добавляем карту в H1
                    else return CardFit.H1;
                }
                // если самая сильная линия < самой сильной линии до выкладывания карты
                else return CardFit.L1;

            }

            // если к лидирующим линиям не подходит ничего 
            return CardFit.NotFit;
        }

        /// <summary> Подходимость карты к городу
        /// 0 - не подходит. 
        /// возвращает L*1000+N где
        /// L - размер линий
        /// N = (100 - дополняет ведущую линию копейщиков, 101 - доп вед линию лучников. если доп 2 вед линию, складываем.)
        /// Ведущая линия - это линия из максимального количества бойцов, которые могут копиться в городе
        /// </summary>
        static int GetFitVal(Town town, bool[] Troops)
        {
            if (town.card.Count == 0 || town.card.Count == 4) throw new Exception("Ошибка вычисления подходимости карты к городу: нельзя вычислисть подходимость к пустому или заполненному городу");

            // смотрим ведущие линии (меньше 2-х не смотрим)
            int L = 4;
            bool[] psb = town.GetCollectedTroops(4);
            if (Card.GetWeight(psb) == 0) { psb = town.GetCollectedTroops(3); L = 3; }
            if (Card.GetWeight(psb) == 0) { psb = town.GetCollectedTroops(2); L = 2; }

            // считаем подходимость
            int ret = 0;
            for (int tr = 0; tr < 5; tr++)
                if (psb[tr] && Troops[tr])
                    ret += 100 + tr;

            return ret == 0 ? 0 : L * 1000 + ret;
        }

        /// <summary>Наиболее подходящая карта к конфликтному городу</summary>
        static int GetMostFitCardInFightTown(CardFit[,] fits, TownType[] TownSt, List<Card> Cards, int numTown, out CardFit cf)
        {
            // это карты, которые самые сильные по подходимости (FitMoreStrong, FitAll, Fit)
            // из этих карт выбираются те, которые подходят к минимальному количеству городов
            // из этих карт выбирается самая слабая карта
            //************************************************

            if (TownSt[numTown] != TownType.FightTown) throw new Exception("Ошибка GetMostFitCardInFightTown. Попытка рассчета для НЕ конфликтного города");

            AI.AddAILog("ДЛЯ ГОРОДА '" + (numTown + 1).ToString() + "'", 1);

            // список всех подходящих карт и выбор максимальной подходимости
            List<int> ci = new List<int>();
            CardFit maxFit = CardFit.Fit;
            for (int c = 0; c < Cards.Count; c++)
            {
                if (fits[numTown, c] == CardFit.Fit || fits[numTown, c] == CardFit.FitMoreStrong)
                {
                    if (fits[numTown, c] == CardFit.FitMoreStrong) maxFit = CardFit.FitMoreStrong;
                    ci.Add(c);
                }
            }
            // если подходящих карт нет
            if (ci.Count == 0)
            {
                cf = CardFit.NotFit;
                AI.AddAILog("подходящих карт НЕТ", 2);
                return -1;
            }

            // для лога
            string s = "подходящие карты:"; foreach (int c in ci) s += c.ToString() + " (" + fits[numTown, c].ToString() + ")   "; AI.AddAILog(s, 2);

            cf = maxFit;
            AI.AddAILog("максимальная подходимость: " + cf.ToString(), 2);

            //  отсев только по максимальной подходимости
            for (int i = 0; i < ci.Count; i++) if (fits[numTown, ci[i]] != maxFit) { ci.RemoveAt(i); i--; }
            s = "отсев только по максимальной подходимости. Остались: "; foreach (int c in ci) s += c.ToString() + " "; AI.AddAILog(s, 2);

            #region *** отсев по количеству городов (нужно минимальное) ***
            int minFitTown = 5;
            int[] cntFitTown = new int[Cards.Count];
            foreach (int c in ci)
            {
                cntFitTown[c] = 0;
                for (int t = 0; t < 5; t++)
                {
                    if (TownSt[t] == TownType.FightTown)
                        if (fits[t, c] == CardFit.Fit || fits[t, c] == CardFit.FitMoreStrong)
                            cntFitTown[c]++;
                }
                if (minFitTown > cntFitTown[c]) minFitTown = cntFitTown[c];
            }
            if (minFitTown < 1) throw new Exception("Ошибка GetMostFitCardInFightTown. Полный отсев 1");

            s = "Количество городов для карт: "; foreach (int c in ci) s += "карта:" + c.ToString() + " кол-во:" + cntFitTown[c].ToString() + "   "; AI.AddAILog(s, 2);

            // отсев
            for (int i = 0; i < ci.Count; i++) if (cntFitTown[ci[i]] != minFitTown) { ci.RemoveAt(i); i--; }

            s = "отсев по минимальному количеству городов Осталось: "; foreach (int c in ci) s += c.ToString() + " "; AI.AddAILog(s, 2);

            #endregion *** отсев по количеству городов (нужно минимальное) ***

            //  отсев по силе (выбирается самая сильная)
            int ret = GetMinWeightInd(Cards, ci);
            AI.AddAILog("отсев по минимальной силе. Осталась карта " + ret.ToString(), 2);

            return ret;

        }

        /// <summary>Наиболее подходящая карта к наиболее подходящему конфликтному городу</summary>
        static CardPoint GetMostFitCardPointInFightTown(CardFit[,] fits, TownType[] TownSt, Town[] Towns, List<Card> Cards, bool bNotLastCard)
        {
            // список городов, наиболее подходящей карты и подходимость карты
            List<int> ti = new List<int>();
            List<int> ci = new List<int>();
            List<CardFit> fi = new List<CardFit>();

            // смотрим города
            for (int t = 0; t < 5; t++)
            {
                // если город не конфликтный, переходим к следующему
                if (TownSt[t] != TownType.FightTown) continue;
                if (bNotLastCard && Towns[t].card.Count > 2) continue;

                // находим наиболее подходящую карту для города
                CardFit cf;
                int c = GetMostFitCardInFightTown(fits, TownSt, Cards, t, out cf);
                if (c != -1)
                {
                    ti.Add(t);
                    ci.Add(c);
                    fi.Add(cf);
                }
            }

            if (ti.Count == 1) return new CardPoint(ti[0], ci[0]);

            AI.AddAILog("СПИСОК ВЫБРАННЫХ ГОРОДОВ С КАРТАМИ: ", 1);
            for (int i = 0; i < ti.Count; i++)
                AI.AddAILog("город '" + (ti[i] + 1).ToString() + "' карта: " + ci[i].ToString() + " подходимость: " + fi[i].ToString(), 2);

            // если карты найдены
            if (ti.Count > 0)
            {
                AI.AddAILog("ВЫБОР ГОРОДА, КУДА КЛАСТЬ", 1);

                // находим наиболее подходящую карту
                // это карты, которые самые сильные по подходимости (FitMoreStrong, FitAll, Fit)
                // из этих карт выбирается самая слабая карта
                //*************************************************

                // выбор максимальной подходимости
                CardFit maxFit = CardFit.Fit;
                foreach (CardFit f in fi) if (f == CardFit.FitMoreStrong) { maxFit = f; break; }

                //  отсев по максимальной подходимости
                for (int i = 0; i < ti.Count; i++) if (fi[i] != maxFit) { ti.RemoveAt(i); ci.RemoveAt(i); fi.RemoveAt(i); i--; }

                //  отсев по силе (выбирается самая слабая)
                CardPoint p = new CardPoint();
                p.NumTown = ti[0];
                p.NumCardInHand = ci[0];
                int minI = 0;
                for (int i = 1; i < ti.Count; i++)
                    if (Card.GetWeight(Cards[p.NumCardInHand].troop) > Card.GetWeight(Cards[ci[i]].troop))
                    {
                        p.NumTown = ti[i];
                        p.NumCardInHand = ci[i];
                        minI = i;
                    }

                return p;
            }
            return new CardPoint(-1, -1);
        }

        static RYGStat RedYellowGreenCardIsExist(Town[] towns)
        {
            // пробегаемся по городам и смотрим, копятся ли уже красно-желтые, и красно-зеленые
            bool bRedYellow = false; // красно-желтые
            bool bRedGreen = false; // красно-зеленые
            int RedYellowGreenCount = 0; // красно-желто-зеленые (могут заменять и те и те)
            for (int t = 0; t < 5; t++)
            {
                if (towns[t].card.Count == 0) continue;

                bool[] clt4 = towns[t].GetCollectedTroops(4);
                if (clt4[4] && clt4[3] && clt4[2])
                {
                    RedYellowGreenCount++;
                }
                else
                {
                    if (clt4[4] && clt4[3]) bRedYellow = true;
                    if (clt4[4] && clt4[2]) bRedGreen = true;
                }
            }
            // если красно-желто-зеленых - 2
            if (RedYellowGreenCount >= 2) return RYGStat.RedYellow_And_RedGreen;

            // если красно-желтые и красно-зеленые
            if (bRedYellow && bRedGreen) return RYGStat.RedYellow_And_RedGreen;

            // если (или красно-желтые или красно-зеленые) И есть красно-желто-зеленые
            if ((bRedYellow || bRedGreen) && RedYellowGreenCount > 0) return RYGStat.RedYellow_And_RedGreen;

            if (bRedYellow) return RYGStat.Only_RedYellow;

            if (bRedGreen) return RYGStat.Only_RedGreen;

            return RYGStat.None;
        }

        /// <summary>Город, который скорее всего будет проигран</summary>
        static bool TownIsLikelyLose(List<Town[]> EnemyTowns, Town town, int townInd)
        {
            AI.AddAILog("ВЫЧИСЛЕНИЕ ГОРОДА '" + (townInd + 1).ToString() + "' КАК ВЕРОЯТНО-ПРОИГРЫШНОГО", 1);

            // это город, где накапливыемые линии сильно слабее, чем в городе противника, где он выложил уже 1 карту (в начале игры) 2 или 3 карты (в середине игры)
            // сильно-слабее:
            // - у противника размерность линий больше; 
            // - копится 1 линия, а у противника 2 или больше. Причем, старшая линия у противника не слабже накапливаемой
            // - копится одна линия и у противника тоже. Причем, старшая линия у противника не слабже накапливаемой и у противника выложены 3 карты к середине игры
            // *****************************************************

            // этап игры (подсчет уже выложенных карт)
            int cntCard = 0;
            foreach (Town t in EnemyTowns[0]) cntCard += t.card.Count;

            // начало игры - 0-7 карт (мин 1 карта)
            // вторая четверть - 8-10 карт (мин 2 карты)
            // третья четверть - 11-14 карт (мин 3 карты)
            // конец игры - 15-20 карт (не считать)

            // если конец игры, выходим
            if (cntCard > 14)
            {
                AI.AddAILog("Игра подходит к концу. Тут не будет таких городов", 2);
                return false;
            }

            // выбираем противников, город которых можно брать в рассчет
            // и вычисляем у них накапливаемые линии и размер линий
            List<bool[]> cltList = new List<bool[]>();
            List<int> lineSzList = new List<int>();

            // то же самое, но только где у протичника выложены 3 карты
            List<bool[]> cltList3 = new List<bool[]>();
            List<int> lineSzList3 = new List<int>();

            for (int i = 0; i < EnemyTowns.Count; i++)
            {
                Town enTown = EnemyTowns[i][townInd];
                // если город пуст или заполнен полностью, переходим к следующему
                if (enTown.card.Count == 0 || enTown.card.Count == 4) continue;

                // если карт в городе не достаточно, переходим к следующему
                if (enTown.card.Count < 2 && cntCard > 7) continue;
                if (enTown.card.Count < 3 && cntCard > 10) continue;

                // вычисляем накапливаемые линии и их размерность
                int lineSize = 4;
                bool[] clt = enTown.GetCollectedTroops(4);
                if (Card.GetWeight(clt) == 0) { lineSize = 3; clt = enTown.GetCollectedTroops(3); }
                if (Card.GetWeight(clt) == 0) { lineSize = 2; clt = enTown.GetCollectedTroops(2); }
                if (Card.GetWeight(clt) == 0) { lineSize = 1; clt = enTown.GetCollectedTroops(1); }

                // добавляем
                cltList.Add(clt);
                lineSzList.Add(lineSize);

                if (enTown.card.Count == 3)
                {
                    cltList3.Add(clt);
                    lineSzList3.Add(lineSize);
                }
            }

            // если подходящих городов нет, выходим
            if (cltList.Count == 0)
            {
                AI.AddAILog("Город не проигрышный", 2);
                return false;
            }

            // выбираем сильнейшего.
            bool[] maxEnClt = cltList[0];
            int maxEnLineSize = lineSzList[0];
            for (int i = 1; i < cltList.Count; i++)
            {
                if (maxEnLineSize > lineSzList[i]) continue;
                if (Card.GetWeight(maxEnClt) < Card.GetWeight(cltList[i])) { maxEnClt = cltList[i]; maxEnLineSize = lineSzList[i]; }
            }
            AI.AddAILog("сильнейшая комбинация у противника: Размер линий: " + maxEnLineSize.ToString() + " копление: (" + (new Card(maxEnClt)).ToString() + ")", 2);

            // выбираем сильнейшего для (3)
            bool[] maxEnClt3 = null;
            int maxEnLineSize3 = -1;
            if (cltList3.Count > 0)
            {
                maxEnClt3 = cltList3[0];
                maxEnLineSize3 = lineSzList3[0];
                for (int i = 1; i < cltList3.Count; i++)
                {
                    if (maxEnLineSize3 > lineSzList3[i]) continue;
                    if (Card.GetWeight(maxEnClt3) < Card.GetWeight(cltList3[i])) { maxEnClt3 = cltList3[i]; maxEnLineSize3 = lineSzList3[i]; }
                }
                AI.AddAILog("сильнейшая комбинация у противника, где выложено 3 карты: Размер линий: " + maxEnLineSize.ToString() + " копление: (" + (new Card(maxEnClt)).ToString() + ")", 2);
            }
            else
            {
                AI.AddAILog("сильнейшая комбинация у противника, где выложено 3 карты НЕТ", 2);
            }

            // считаем накапливаемые линии игрока
            int botLSize = 4;
            bool[] botClt = town.GetCollectedTroops(4);
            if (Card.GetWeight(botClt) == 0) { botLSize = 3; botClt = town.GetCollectedTroops(3); }
            if (Card.GetWeight(botClt) == 0) { botLSize = 2; botClt = town.GetCollectedTroops(2); }
            if (Card.GetWeight(botClt) == 0) { botLSize = 1; botClt = town.GetCollectedTroops(1); }

            AI.AddAILog("Комбинация у бота: Размер линий: " + botLSize.ToString() + " копление: (" + (new Card(botClt)).ToString() + ")", 2);

            // если у бота размер линий меньше
            if (botLSize < maxEnLineSize)
            {
                AI.AddAILog("ГОРОД ВЕРОЯТНО-ПРОИГРЫШНЫЙ у противника размерность линий больше", 2);
                return true;
            }
            // если у бота размер лини больше
            if (botLSize > maxEnLineSize)
            {
                AI.AddAILog("Город не проигрышный", 2);
                return false;
            }

            // смотрим линии, где у бота только 1 линия, а у противника 2 или больше. Причем, старшая линия у противника не слабже накапливаемой 
            // если у бота линий меньше 2-х и меньше, чем у противника
            int botCntLine = Card.GetWeight(botClt) / 100;
            if (botCntLine < 2 && botCntLine < (Card.GetWeight(maxEnClt) / 100))
            {
                // если единственная линия бота младше старшей линии противника, город проигрышный
                if (GetHighLine(botClt) < GetHighLine(maxEnClt))
                {
                    AI.AddAILog("ГОРОД ВЕРОЯТНО-ПРОИГРЫШНЫЙ у бота копится 1 линия, а у противника 2 или больше. Причем, старшая линия у противника не слабже накапливаемой", 2);
                    return true;
                }
            }


            // если у бота копится одна линия и у противника тоже. Причем, старшая линия у противника не слабже накапливаемой и у противника выложены 3 карты к середине игры
            if (cltList3.Count > 0 && botCntLine == 1 && (Card.GetWeight(maxEnClt3) / 100) == 1)
            {
                // смотрим линии (на старшинство)
                // если единственная линия бота младше старшей линии противника, город проигрышный
                if (GetHighLine(botClt) < GetHighLine(maxEnClt))
                {
                    AI.AddAILog("ГОРОД ВЕРОЯТНО-ПРОИГРЫШНЫЙ поскольку у противника накоплено уже 3 карты в его единственной линии и линия старше, чем единственная линия бота", 2);
                    return true;
                }

            }

            AI.AddAILog("Город не проигрышный", 2);
            return false;
        }

        /// <summary>Старшая накапливаемая линия</summary>
        static int GetHighLine(bool[] troopers)
        {
            return GetHighLine(troopers, 4);
        }

        /// <summary>Старшая накапливаемая линия</summary>
        /// <param name="maxLine">максимальная линия от которой смотреть (включительно)</param>
        static int GetHighLine(bool[] troopers, int maxLine)
        {
            for (int tr = maxLine; tr >= 0; tr--)
                if (troopers[tr]) return tr;
            return -1;
        }

        /// <summary>Определение сливных карт</summary>
        static List<int> GetDropCards(List<int> DropTowns, Town[] Towns, List<Card> Cards, TownType[] TownSt)
        {
            // явно-сливная карта - это карта с одиночной картинкой ниже красной, и, при этом, такая картинка не подходит ни к одной лидирующей линии для НЕ сливных городов
            // либо - (если все города заявлены) НЕ одиночная карта, каждая картинка не подходит ни к одному городу И у карты только 2 картинки без красных
            //*********************************************************************

            List<int> dropCards = new List<int>();

            // смотрим, есть ли пустые города
            bool bEmptyTownsExist = false;
            foreach (Town town in Towns) if (town.card.Count == 0) { bEmptyTownsExist = true; break; }

            // если пустых городов более нет, можно смотреть карты на 2 линии, иначе только на одну
            int maxTroopers = bEmptyTownsExist ? 1 : 2;

            for (int c = 0; c < Cards.Count; c++)
            {
                // смотрим, подходит ли карту куда-либо
                bool bNotFit = true; // карта не подходит ни к одному НЕ сливному городу
                for (int t = 0; t < 5; t++)
                {
                    // если город сливной, переходим к следующиему
                    if (DropTowns.Contains(t)) continue;
                    // если город пуст или полон, переходим к следующиему
                    if (Towns[t].card.Count == 0 || Towns[t].card.Count == 4) continue;

                    // проверка подходимости к лидирующей линии4 города для одиночной карты
                    if (maxTroopers == 1)
                    {
                        int townHLine4_ind = GetHighLine(Towns[t].GetCollectedTroops(4));
                        if (townHLine4_ind == -1) continue;
                        if (Cards[c].troop[townHLine4_ind]) { bNotFit = false; break; }
                    }
                    // проверка для 2-линии карты (если есть подходимость не более 1 линии и эта линия не лидирующая)
                    else
                    {
                        // вычисляем накапливаемые линии (размерностью 4 и 3. меньше - не актуально)
                        bool[] clt = Towns[t].GetCollectedTroops(4);
                        if (Card.GetWeight(clt) == 0) clt = Towns[t].GetCollectedTroops(3);
                        int townHLine_ind34 = GetHighLine(clt);
                        if (townHLine_ind34 == -1) continue;

                        // смотрим совпадение старшей линии
                        if (Cards[c].troop[townHLine_ind34]) { bNotFit = false; break; }

                        // смотрим совпадения по другим линиям
                        // создаем новый 'город' состоящий из коллекта и новой карты
                        Town tn = new Town();
                        tn.card.Add(new Card(clt));
                        tn.card.Add(Cards[c].Clone());
                        if (Card.GetWeight(tn.GetCollectedTroops(4)) >= 200) { bNotFit = false; break; }

                    }
                }

                // если карта никуда не подходит, у нее не более 2-х линий и нет красных
                if (bNotFit && (Card.GetWeight(Cards[c].troop) / 100) <= maxTroopers && !Cards[c].troop[4])
                {
                    AI.AddAILog("сливная карта: " + c.ToString() + " (" + Cards[c].ToString() + ")", 1);
                    dropCards.Add(c);
                }
            }

            return dropCards;
        }

        #endregion *** ФИИ ****************************************************

        #region ФИИ Шагов ****************************************************

        static CardPoint Step_DropCard(Town[] towns, List<Card> cards, TownType[] TownSt)
        {
            CardPoint retNull = new CardPoint(-1, -1);

            AI.AddAILog("ОПРЕДЕЛЕНИЕ ЯВНО-СЛИВНЫХ КАРТ", 1);
            List<int> DropTowns = new List<int>();
            for (int t = 0; t < 5; t++) if (TownSt[t] == TownType.DropTown) DropTowns.Add(t);
            List<int> dropCards = GetDropCards(DropTowns, towns, cards, TownSt);

            // если явно-сливных карт нет
            if (dropCards.Count == 0)
            {
                AI.AddAILog("Явно-сливных карт нет", 1);
                return retNull;
            }

            // выбирается самая слабая
            int dropCardInd = -1;
            int minWeight = int.MaxValue;
            foreach (int c in dropCards)
            {
                int actWeight = Card.GetWeight(cards[c].troop);
                if (minWeight > actWeight) { minWeight = actWeight; dropCardInd = c; }
            }

            // выбор сливного города к которому карта подходит лучше всего
            int dropTownInd = -1;
            int maxFitVal = int.MinValue;
            for (int t = 0; t < 5; t++)
            {
                // только, если город сливной
                if (TownSt[t] == TownType.DropTown)
                {
                    int fitVal = GetFitVal(towns[t], cards[dropCardInd].troop);
                    AI.AddAILog("подходимость сливного города " + t.ToString() + " для сливной карты = " + fitVal.ToString(), 1);
                    if (maxFitVal < fitVal)
                    {
                        maxFitVal = fitVal;
                        dropTownInd = t;
                    }
                }
            }

            if (dropTownInd != -1)
            {
                AI.AddAILog("Слив карты в город " + dropTownInd.ToString(), 1);
                return new CardPoint(dropTownInd, dropCardInd);
            }

            AI.AddAILog("Явно-сливных городов нет", 1);
            return retNull;
        }

        static CardPoint Step_MegaStrongCard(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards, TownType[] TownSt, int AllEmptyTownInd)
        {
            // такая карта может выкладываться, если город еще не заявлен
            // мегасильная карта - это (по нисходящей):
            // - все пять или 
            // - первые 4 или
            // - или первые 3, если не копится сразу красно-желтых, и красно-зеленых
            // - красно-желтая, если такой комбинации еще не копится или
            // - или красно-зеленая, если такой комбинации еще не копится

            // Сначала пытаться выложить карты MoreStrong для городов, которые заявлены уже всеми противниками
            // потом в полностью пустые города
            //*******************************************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            AI.AddAILog("ЗАПОЛНЕНИЕ СПИСКА МЕГАСИЛЬНЫХ КАРТ", 1);
            List<int> ci = new List<int>();

            #region *** заполнение списка мегасильных карт ***

            // смотрим, копятся ли уже красно-желтые, и красно-зеленые
            RYGStat RYG = RedYellowGreenCardIsExist(towns);

            // - все пять или первые 4 если не копится сразу красно-желтых, и красно-зеленых
            for (int c = 0; c < cards.Count; c++)
            {
                int weight = Card.GetWeight(cards[c].troop);
                if (weight == 510 || weight == 410)
                {
                    if (RYG != RYGStat.RedYellow_And_RedGreen)
                    {
                        if (!ci.Contains(c))
                        {
                            ci.Add(c);
                            AI.AddAILog("все пять или первые 4:" + c.ToString(), 2);
                        }
                    }
                }
            }

            // - первые 3, если не копится сразу красно-желтых, и красно-зеленых
            for (int c = 0; c < cards.Count; c++)
            {
                if (cards[c].troop[2] && cards[c].troop[3] && cards[c].troop[4])
                {
                    if (RYG != RYGStat.RedYellow_And_RedGreen)
                    {
                        if (!ci.Contains(c))
                        {
                            ci.Add(c);
                            AI.AddAILog("карта с заявкой на первые 3:" + c.ToString(), 2);
                        }
                    }
                }
            }

            // - красно-желтая, если такой комбинации еще не копится или красно-зеленая, если такой комбинации еще не копится
            for (int c = 0; c < cards.Count; c++)
            {
                if (cards[c].troop[3] && cards[c].troop[4])
                {
                    if (RYG != RYGStat.Only_RedYellow && RYG != RYGStat.RedYellow_And_RedGreen)
                    {
                        if (!ci.Contains(c))
                        {
                            ci.Add(c);
                            AI.AddAILog("карта с заявкой на красно-желтые:" + c.ToString(), 2);
                        }
                    }
                }

                if (cards[c].troop[2] && cards[c].troop[4])
                {
                    if (RYG != RYGStat.Only_RedGreen && RYG != RYGStat.RedYellow_And_RedGreen)
                    {
                        if (!ci.Contains(c))
                        {
                            ci.Add(c);
                            AI.AddAILog("карта с заявкой на красно-зеленые:" + c.ToString(), 2);
                        }
                    }
                }
            }

            #endregion *** заполнение списка мегасильных карт ***

            if (ci.Count == 0) { AI.AddAILog("мегасильных карт нет", 2); return retNull; }

            #region *** попытка выложить карту явно сильнее в город, заявленный всеми противниками ***
            for (int t = 0; t < 5; t++)
            {
                // если город заявлен уже всеми противниками, а у бота он пока пуст
                if (TownSt[t] == TownType.BotEmptyClmEnemyALL)
                {
                    // смотрим самую сильную комбинацию противника
                    bool[] enMaxClt = GetMaxEnemyCollect(EnemyTowns, t, 4);

                    // сравниваем
                    foreach (int c in ci)
                    {
                        CollectCompareResult res = CollectCompare(cards[c].troop, enMaxClt);
                        if (res == CollectCompareResult.Collect1_MoreStrong)
                        {
                            AI.AddAILog("Выложена мегасильная карта " + c.ToString() + " (явно сильнее) в город '" + (t + 1).ToString() + "', заявленный уже всеми противниками", 1);
                            return new CardPoint(t, c);
                        }
                    }
                }
            }
            #endregion *** попытка выложить карту явно сильнее в город, заявленный всеми противниками ***

            #region *** попытка выложить карту в полностью пустой город ***
            if (AllEmptyTownInd != -1)
            {
                AI.AddAILog("Выложена мегасильная карта " + ci[0].ToString() + " в полностью пустой город '" + (AllEmptyTownInd + 1).ToString(), 1);
                return new CardPoint(AllEmptyTownInd, ci[0]);
            }
            #endregion *** попытка выложить карту явно сильнее в город, заявленный всеми противниками ***

            AI.AddAILog("Мегасильная карта НЕ выложена", 1);
            return retNull;
        }

        static CardPoint Step_CardInFightTown(Town[] towns, List<Card> cards, TownType[] TownSt, CardFit[,] fits)
        {
            CardPoint retNull = new CardPoint(-1, -1);

            // выкладывается в порядке приоритета:
            // сначала карта для города, в который уже что-то выложено, но где осталось выложить более одной карты
            AI.AddAILog("ДЛЯ ГОРОДОВ, ГДЕ ВЫЛОЖЕНЫ 1-2 КАРТЫ", 1);
            CardPoint cp = GetMostFitCardPointInFightTown(fits, TownSt, towns, cards, true);
            if (cp.NumTown != -1)
            {
                AI.AddAILog("Выкложена карта в город, в который уже выложна только 1 или 2 карты", 1);
                return cp;
            }
            // потом для города, где осталось выложить последнюю карту
            AI.AddAILog("ДЛЯ ГОРОДОВ, ГДЕ ОСТАЛОСЬ ВЫЛОЖИТЬ ПОСЛЕДНЮЮ КАРТУ", 1);
            cp = GetMostFitCardPointInFightTown(fits, TownSt, towns, cards, false);
            if (cp.NumTown != -1)
            {
                AI.AddAILog("Выкложена последняя карта в город", 1);
                return cp;
            }

            AI.AddAILog("подходящих карт нет", 1);
            return retNull;
        }

        static CardPoint Step_CardInOnlyBotTown(Town[] towns, List<Card> cards, TownType[] TownSt, CardFit[,] fits, CardFit NecessaryCardFit)
        {
            // смотрим есть ли в городах OnlyBot подходящие карты. 
            // выбираются города со статусом NecessaryCardFit
            // из них выкладывается карта в любой город с минимальным количеством карт
            // если нет, то в город, где осталось выложить последнюю
            //*********************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            List<int> ti = new List<int>();   // список городов с NecessaryCardFit картами
            List<int> ci = new List<int>(); // список карт для городов, который нужно выложить
            for (int t = 0; t < 5; t++)
            {
                bool bExist = false;
                for (int c = 0; c < cards.Count; c++) if (fits[t, c] == NecessaryCardFit && TownSt[t] == TownType.OnlyBot) { ci.Add(c); bExist = true; break; }
                if (bExist) ti.Add(t);
            }

            if (ti.Count > 0)
            {
                // отсеять городов, по минимальному количеству карт
                int minCardInTown = 4;
                foreach (int t in ti) if (minCardInTown > towns[t].card.Count) minCardInTown = towns[t].card.Count;
                for (int i = 0; i < ti.Count; i++) if (towns[ti[i]].card.Count != minCardInTown) { ti.RemoveAt(i); ci.RemoveAt(i); i--; }

                // выкладывание карты в любой из оставшихся городов
                int ind = rnd.Next(ti.Count);

                CardPoint p = new CardPoint(ti[ind], ci[ind]);
                AI.AddAILog("выложена карта (OnlyBot - " + NecessaryCardFit.ToString() + ") карта " + p.NumCardInHand + " в город " + p.NumTown, 1);
                return p;

            }

            AI.AddAILog("подходящих карт нет", 1);
            return retNull;
        }

        static CardPoint Step_CardInEmptyBotAndAllFillEnemy(List<Card> cards, TownType[] TownSt, CardFit[,] fits, CardFit NecessaryCardFit)
        {
            CardPoint retNull = new CardPoint(-1, -1);

            // выбираем самую сильную карту NecessaryCardFit и выкладываем ее (чтобы была максимально многозаявочная)
            int tInd = -1;
            int cInd = -1;
            int MaxWeight = int.MinValue;
            for (int t = 0; t < 5; t++)
            {
                if (TownSt[t] != TownType.BotEmptyClmEnemyALL) continue;
                for (int c = 0; c < cards.Count; c++)
                {
                    if (fits[t, c] == NecessaryCardFit)
                    {
                        int wgt = Card.GetWeight(cards[c].troop);
                        if (MaxWeight < wgt)
                        {
                            tInd = t;
                            cInd = c;
                            MaxWeight = wgt;
                        }
                    }
                }
            }

            if (tInd != -1)
            {
                CardPoint p = new CardPoint(tInd, cInd);
                AI.AddAILog("ВЫЛОЖЕНА КАРТА (" + NecessaryCardFit.ToString() + ") " + p.NumCardInHand + " в город " + p.NumTown, 1);
                return p;
            }

            AI.AddAILog(NecessaryCardFit.ToString() + " карт ИЛИ пустых городов нет", 1);
            return retNull;
        }

        static CardPoint Step_DropInEnemyFightTown(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards, TownType[] TownSt)
        {
            // для каждого города смотрим всех противников
            // если у бота город пуст, а минимум у двух противников копится по 2 линии с красными, говорим, что можно сливать
            // из таких городов выбираем город, заявленный самыми сильными картами противников
            //****************************************************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            // если игроков всего двое, выходим
            if (EnemyTowns.Count == 1)
            {
                AI.AddAILog("пропуск, поскольку всего 2 игрока", 1);
                return retNull;
            }

            // вес накапливаемых линий у самого сильного противника, где у бота город пуст, а минимум у двух противников копится по 2 линии с красными
            List<int> clt4WeightL = new List<int>();
            List<int> townInd = new List<int>(); // города, куда сливать
            for (int t = 0; t < 5; t++)
            {
                if (TownSt[t] == TownType.DropTown) { townInd.Add(t); continue; }

                if (TownSt[t] == TownType.BotEmptyClmEnemy || TownSt[t] == TownType.BotEmptyClmEnemyALL)
                {
                    // проверка карт противников
                    int cntFightEnemyWith2LinesWithRed = 0;
                    int maxClt4Weight = 0;
                    // пробегаемся по противникам для текущего города
                    for (int pl = 0; pl < EnemyTowns.Count; pl++)
                    {
                        if (EnemyTowns[pl][t].card.Count == 0) continue; // если город пуст
                        bool[] clt4 = EnemyTowns[pl][t].GetCollectedTroops(4);
                        if (!clt4[4]) continue; // если нет красной
                        int clt4Weight = Card.GetWeight(clt4);
                        // если меньше двух линий
                        if (clt4Weight < 200) continue;

                        cntFightEnemyWith2LinesWithRed++;
                        if (maxClt4Weight < clt4Weight) maxClt4Weight = clt4Weight;
                    }

                    // если ротивников оказалось 2 и более
                    if (cntFightEnemyWith2LinesWithRed > 1)
                    {
                        clt4WeightL.Add(maxClt4Weight);
                        townInd.Add(t);
                    }
                }
            }

            AI.AddAILog("ОПРЕДЕЛЕНИЕ СЛИВНЫХ КАРТ", 1);
            List<int> dropCards = GetDropCards(townInd, towns, cards, TownSt);
            // если явно-сливных карт нет
            if (dropCards.Count == 0)
            {
                AI.AddAILog("сливных карт нет", 1);
                return retNull;
            }

            if (townInd.Count == 0)
            {
                AI.AddAILog("пустых городов у бота или достаточно конфликтных городов противника нет", 1);
                return retNull;
            }
            else
            {
                // выбирается самая слабая
                int dropCardInd = -1;
                int minWeight = int.MaxValue;
                foreach (int c in dropCards)
                {
                    int actWeight = Card.GetWeight(cards[c].troop);
                    if (minWeight > actWeight) { minWeight = actWeight; dropCardInd = c; }
                }

                // выбор самого сильного конфликта
                int maxConflTownInd = townInd[0];
                int maxClt4Weight = clt4WeightL[0];
                for (int i = 1; i < clt4WeightL.Count; i++)
                {
                    if (maxClt4Weight < clt4WeightL[i])
                    {
                        maxClt4Weight = clt4WeightL[i];
                        maxConflTownInd = townInd[i];
                    }
                }

                CardPoint p = new CardPoint(maxConflTownInd, dropCardInd);
                AI.AddAILog("выложена сливная карта", 1);
                return p;
            }

        }

        static CardPoint Step_MostFitCard(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards, TownType[] TownSt)
        {
            // выбирается для каждого незаполненного города самая подходящая карта
            // самая подходящая, это карта, которая будет копить максимальное количество линий.
            // и из этого, максимально-старшие линии

            // из таких городов выбираются те, у которых у противников еще не полностью заполнены города (если нет, то берем с заполненными. тут заведомый проигрыш, но уже ничего не поделаешь)
            // из таких городов выбираются те, у которых остается макcимальное количество линий4 (если нет, то линий3, и т.д.)
            // из таких городов выбираются города, которые всех меньше заполнены (так как для них меньше всех вероятность полного накопления)
            // из оставшихся городов выбирается любой

            //****************************************************************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            AI.AddAILog("ВЫБОР КАРТЫ ДЛЯ КАЖДОГО ГОРОДА", 1);
            int[] crdInd = new int[5];
            int[] cltWeightWithNumLines = new int[5]; // вее накапливаемых линий + 1000*рамерность линий
            for (int t = 0; t < 5; t++)
            {
                AI.AddAILog("ГОРОД '" + (t + 1).ToString() + "':", 2);
                crdInd[t] = -1;
                if (TownSt[t] == TownType.FullTown) continue;

                int maxCrtInd = 0; // по умолчанию: если что, то будет самая первая карта 
                int maxWeight = 0; // по умолчанию: ничего не подходит
                // смотрим накапливаемые линии для каждой карты и выбираем максимум
                for (int c = 0; c < cards.Count; c++)
                {
                    int cltW = GetFitVal(towns[t], cards[c].troop);
                    if (maxWeight < cltW)
                    {
                        maxCrtInd = c;
                        maxWeight = cltW;
                    }
                }
                crdInd[t] = maxCrtInd;
                cltWeightWithNumLines[t] = maxWeight;

                AI.AddAILog(" наиболее подходит карта " + crdInd[t].ToString() + " c L-весом " + cltWeightWithNumLines[t], 2);
            }

            // лист подходящих городов
            List<int> ti = new List<int>();
            int tInd = -1; // любой незаполненный город
            for (int t = 0; t < 5; t++)
            {
                if (crdInd[t] != -1 && cltWeightWithNumLines[t] > 0) { ti.Add(t); }
                else if (TownSt[t] != TownType.FullTown) tInd = t;
            }

            // если подходящих городов нет, выкладываем первую карту в ближайший город
            if (ti.Count == 0)
            {
                AI.AddAILog("ВЫЛОЖЕНА ПРОСТО ПЕРВАЯ КАРТА, так как подходящий нет", 1);
                return new CardPoint(tInd, 0);
            }

            AI.AddAILog("ОТСЕВ ГОРОДОВ", 1);
            AI.AddAILog("ПОДХОДЯЩИЕ ГОРОДА: ", 2); foreach (int t in ti) AI.AddAILog("'" + (t + 1).ToString() + "' (карта:" + crdInd[t].ToString() + ", вес:" + cltWeightWithNumLines[t] + ")", 3);

            // максимальное заполнение противником в каждом городе
            int[] maxKolVoEnemyCard = new int[5];
            for (int t = 0; t < 5; t++)
            {
                // минимум будет искаться среди максимального заполнения каждого города
                maxKolVoEnemyCard[t] = 0;
                for (int pl = 0; pl < EnemyTowns.Count; pl++)
                {
                    if (maxKolVoEnemyCard[t] < EnemyTowns[pl][t].card.Count)
                        maxKolVoEnemyCard[t] = EnemyTowns[pl][t].card.Count;
                }
            }

            #region *** выбор городов только где у противников заполненено меньше 4 карт ***

            // выбор минимума у противника
            int minKolVoEnemyCard = 4;
            foreach (int t in ti)
            {
                if (minKolVoEnemyCard > maxKolVoEnemyCard[t])
                    minKolVoEnemyCard = maxKolVoEnemyCard[t];
            }
            AI.AddAILog("минимальное заполнение вражеских городов = " + minKolVoEnemyCard.ToString(), 2);

            // отсев (если минимум - это не 4 города)
            if (minKolVoEnemyCard < 4)
            {
                for (int i = 0; i < ti.Count; i++) if (maxKolVoEnemyCard[ti[i]] > 3) { ti.RemoveAt(i); i--; }
                AI.AddAILog("выбор городов где у противника заполнение менее 4. Остались:", 2); foreach (int t in ti) AI.AddAILog("'" + (t + 1).ToString() + "' (карта:" + crdInd[t].ToString() + ", вес:" + cltWeightWithNumLines[t] + ")", 3);
            }

            #endregion *** выбор городов только где у противников заполненено меньше 4 карт ***

            // выбор городов, у которых линии максимально длинные
            int maxLSize = 1;
            foreach (int t in ti) if (maxLSize < (cltWeightWithNumLines[t] / 1000)) { maxLSize = cltWeightWithNumLines[t] / 1000; }
            for (int i = 0; i < ti.Count; i++) if ((cltWeightWithNumLines[ti[i]] / 1000) != maxLSize) { ti.RemoveAt(i); i--; }
            AI.AddAILog("выбор городов, у которых линии максимально длинные. Остались: ", 2); foreach (int t in ti) AI.AddAILog("'" + (t + 1).ToString() + "' (карта:" + crdInd[t].ToString() + ", вес:" + cltWeightWithNumLines[t] + ")", 3);

            // выбор городов с максимальным количеством линий
            int maxCntLines = 1;
            foreach (int t in ti) if (maxCntLines < (cltWeightWithNumLines[t] / 100)) { maxCntLines = cltWeightWithNumLines[t] / 100; }
            for (int i = 0; i < ti.Count; i++) if ((cltWeightWithNumLines[ti[i]] / 100) != maxCntLines) { ti.RemoveAt(i); i--; }
            AI.AddAILog("выбор городов с максимальным количеством линий. Остались: ", 2); foreach (int t in ti) AI.AddAILog("'" + (t + 1).ToString() + "' (карта:" + crdInd[t].ToString() + ", вес:" + cltWeightWithNumLines[t] + ")", 3);

            // минимальное заполнение (из оставшихся)
            int minKolVoCard = 4;
            foreach (int t in ti) if (minKolVoCard > towns[t].card.Count) minKolVoCard = towns[t].card.Count;
            // выбор городов только с минимальным заполнением
            for (int i = 0; i < ti.Count; i++) if (towns[ti[i]].card.Count > minKolVoCard) { ti.RemoveAt(i); i--; }
            AI.AddAILog("выбор городов только с минимальным заполнением. Остались: ", 2); foreach (int t in ti) AI.AddAILog("'" + (t + 1).ToString() + "' (карта:" + crdInd[t].ToString() + ", вес:" + cltWeightWithNumLines[t] + ")", 3);

            // выкладывание карты в первый попывшийся из оставшихся
            AI.AddAILog("карта выложена", 1);
            return new CardPoint(ti[0], crdInd[ti[0]]);
        }

        static CardPoint Step_DropCardInLikelyLoseTown(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards, TownType[] TownSt)
        {
            // попытка слить явно-сливную карту в город, который будет скорее всего проигран
            // из таких городов выбирается самый слабый
            // к нему подбирается наиболее подходящая из сливных карт

            CardPoint retNull = new CardPoint(-1, -1);

            // поиск городов
            List<int> ti = new List<int>();
            for (int t = 0; t < 5; t++)
            {
                // только не пустые и не полностью полные города
                if (towns[t].card.Count == 0 || towns[t].card.Count == 4) continue;

                if (TownSt[t] == TownType.DropTown || TownIsLikelyLose(EnemyTowns, towns[t], t))
                    ti.Add(t);
            }

            if (ti.Count == 0)
            {
                AI.AddAILog("Городов, которые скорее всего будут проиграны нет", 1);
                return retNull;
            }

            AI.AddAILog("ОПРЕДЕЛЕНИЕ СЛИВНЫХ КАРТ", 1);
            List<int> dropCards = GetDropCards(ti, towns, cards, TownSt);
            // если явно-сливных карт нет
            if (dropCards.Count == 0)
            {
                AI.AddAILog("сливных карт нет", 1);
                return retNull;
            }

            // выбор самого слабого города из всех
            int minTownInd = -1;
            int minLClt = int.MaxValue;
            for (int i = 0; i < ti.Count; i++)
            {
                int actLClt = towns[ti[i]].GetCollectedLinesWeight();
                if (minLClt > actLClt) { minLClt = actLClt; minTownInd = ti[i]; }
            }

            // подбираем наиболее подходящую карту для города
            // это карта, где город после выкладывания этой карты будет максимально сильным
            int cardInd = 0;
            int maxLClt = int.MinValue;
            foreach (int c in dropCards)
            {
                Town townWithCard = towns[minTownInd].Clone();
                townWithCard.card.Add(cards[c]);
                int actLClt = townWithCard.GetCollectedLinesWeight();
                if (maxLClt < actLClt) { maxLClt = actLClt; cardInd = c; }
            }

            AI.AddAILog("Выложена карта " + cardInd.ToString() + "в город " + minTownInd.ToString(), 1);
            return new CardPoint(minTownInd, cardInd);
        }

        static CardPoint Step_StrongestCardInEmptyTown(List<Town[]> EnemyTowns, List<Card> cards, TownType[] TownSt)
        {
            // выбирается самая сильная карта
            // выбирается город по принципу:
            // - из пустых городов выбираются те, где у противника всех меньше карт
            // - из таких городов выбирается город с самым слабым коплением2  
            // - из таких городов выбирается самый слабый
            // **********************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            List<int> ti = new List<int>();
            for (int t = 0; t < 5; t++) if (TownSt[t] == TownType.BotEmptyClmEnemy || TownSt[t] == TownType.BotEmptyClmEnemyALL) ti.Add(t);
            if (ti.Count == 0) { AI.AddAILog("пустых городов нет", 1); return retNull; }

            // смотрим минимальное количество заявленых карт
            int MinEnCard = 4;
            int[] MaxCntCard = new int[5];
            int[] MaxLnWeight2 = new int[5];
            int[] MaxLnWeight = new int[5];
            int[] EnLeader = new int[5];
            foreach (int t in ti)
            {
                // выбирается максимальное количество карт в текущем городе
                MaxCntCard[t] = 0;
                MaxLnWeight2[t] = 0;
                MaxLnWeight[t] = 0;
                int LdrPlInd = 0;
                for (int pl = 0; pl < EnemyTowns.Count; pl++)
                {
                    // максимальное количество карт противника в городе
                    if (MaxCntCard[t] < EnemyTowns[pl][t].card.Count) MaxCntCard[t] = EnemyTowns[pl][t].card.Count;
                    // максимальное копление2 в городе
                    int LnWeight = EnemyTowns[pl][t].GetCollectedLinesWeight2();
                    if (MaxLnWeight2[t] < LnWeight) MaxLnWeight2[t] = LnWeight;

                    // лидер в городе
                    // если 
                    if (EnemyTowns[LdrPlInd][t].Leader(EnemyTowns[pl][t]) == Town.LeaderStat.Lose) LdrPlInd = pl;
                }
                EnLeader[t] = LdrPlInd;
                if (MinEnCard > MaxCntCard[t]) MinEnCard = MaxCntCard[t];
            }
            // оставляем только города, где у противника заявлено всх меньше карт
            for (int i = 0; i < ti.Count; i++) { if (MaxCntCard[ti[i]] != MinEnCard) { ti.RemoveAt(i); i--; } }

            // смотрим самое слабое копление2 из оставшихся городов
            int MinLnWeight2 = int.MaxValue;
            foreach (int t in ti) if (MinLnWeight2 > MaxLnWeight2[t]) MinLnWeight2 = MaxLnWeight2[t];
            // оставляем города, где у противника самое слабое копление
            for (int i = 0; i < ti.Count; i++) { if (MaxLnWeight2[ti[i]] != MinLnWeight2) { ti.RemoveAt(i); i--; } }

            // выбираем самый слабый город
            int tInd = ti[0];
            foreach (int t in ti) if (EnemyTowns[EnLeader[t]][t].Leader(EnemyTowns[EnLeader[t]][tInd]) == Town.LeaderStat.Lose) tInd = t;

            CardPoint p = new CardPoint(tInd, GetMaxWeightInd(cards, null));

            AI.AddAILog("выложена карта " + p.NumCardInHand.ToString() + " в город '" + (p.NumTown + 1).ToString() + "'", 1);
            return p;

        }

        static CardPoint Step_MostFitCardWithoutEnemy(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards, TownType[] TownSt, CardFit[,] fits, CardFit NecessaryCardFit)
        {
            // выбираем города для выкладывания с самым малым количеством карт
            // для этих городов выбирается карта с максимальным коплением NecessaryCardFit
            // *****************************************************************

            CardPoint retNull = new CardPoint(-1, -1);

            // лист подходящих городов
            List<int> ti = new List<int>();
            for (int t = 0; t < 5; t++)
            {
                if (TownSt[t] == TownType.FightTown)
                {
                    for (int c = 0; c < cards.Count; c++)
                    {
                        if (fits[t, c] == NecessaryCardFit) { ti.Add(t); break; }
                    }
                }
            }


            // смотрим минимальное количество заявленых карт
            int MinEnCard = 4;
            int[] MaxCntCard = new int[5];
            foreach (int t in ti)
            {
                // выбирается максимальное количество карт в текущем городе
                MaxCntCard[t] = 0;
                for (int pl = 0; pl < EnemyTowns.Count; pl++)
                {
                    // максимальное количество карт противника в городе
                    if (MaxCntCard[t] < EnemyTowns[pl][t].card.Count) MaxCntCard[t] = EnemyTowns[pl][t].card.Count;
                    // максимальное копление2 в городе
                    int LnWeight = EnemyTowns[pl][t].GetCollectedLinesWeight2();
                }
                if (MinEnCard > MaxCntCard[t]) MinEnCard = MaxCntCard[t];
            }
            // оставляем только города, где у противника заявлено всх меньше карт
            for (int i = 0; i < ti.Count; i++) { if (MaxCntCard[ti[i]] != MinEnCard) { ti.RemoveAt(i); i--; } }

            // выбор самого сильного копления
            int tInd = -1;
            int cInd = -1;
            int maxLnWeight = -1;
            foreach (int t in ti)
            {
                for (int c = 0; c < cards.Count; c++)
                {
                    if (fits[t, c] != NecessaryCardFit) continue;

                    int lnWeight = towns[t].GetCollectedLinesWeight2();

                    if (maxLnWeight < lnWeight)
                    {
                        maxLnWeight = lnWeight;
                        tInd = t;
                        cInd = c;
                    }
                }
            }

            if (tInd == -1) { AI.AddAILog("Подходящих карт нет", 1); return retNull; }

            // выбор самой слабой подходящей карты для выбранного города
            List<int> ci = new List<int>();
            for (int c = 0; c < cards.Count; c++) if (fits[tInd, c] == NecessaryCardFit) ci.Add(c);
            cInd = GetMinWeightInd(cards, ci);

            AI.AddAILog("Выложена карта " + cInd.ToString() + " в город '" + (tInd + 1).ToString() + "'", 1);
            return new CardPoint(tInd, cInd);
        }

        #endregion ФИИ Шагов ****************************************************

        public static CardPoint PlayCard(List<Town[]> EnemyTowns, Town[] towns, List<Card> cards)
        {
            CardPoint p = new CardPoint(-1, -1);

            try
            {
                //A
                AI.AddAILog("ОПРЕДЕЛЕНИЕ ПОДХОДИМОСТИ КАРТ", 0);
                #region *** определение типов городов и подходимости карт ***

                // город, карта
                CardFit[,] fits = new CardFit[5, cards.Count];
                TownType[] TownSt = new TownType[5];

                {
                    int[,] fitVals = new int[5, cards.Count];
                    // пробегаемся по всем городам
                    for (int t = 0; t < 5; t++)
                    {
                        AI.AddAILog("ГОРОД '" + (t + 1).ToString() + "'", 1);

                        // если город заполнен, переходим к следующему
                        if (towns[t].card.Count >= 4)
                        {
                            TownSt[t] = TownType.FullTown;
                            AI.AddAILog("город заполнен", 2);
                            continue;
                        }

                        // смотрим, сколькими противниками заявлен город
                        int enemyClaimCount = 0;
                        for (int i = 0; i < EnemyTowns.Count; i++) if (EnemyTowns[i][t].card.Count != 0) { enemyClaimCount++; }

                        #region *** определяем полностью пустой или пустой у бота или заполняемый только ботом ***
                        if (towns[t].card.Count == 0 && enemyClaimCount == 0) { TownSt[t] = TownType.AllEmpty; AI.AddAILog("город полностью пуст", 2); continue; }
                        if (towns[t].card.Count == 0 && enemyClaimCount < EnemyTowns.Count) { TownSt[t] = TownType.BotEmptyClmEnemy; AI.AddAILog("город заявлен только противниками, но не всеми", 2); continue; }
                        #endregion *** определяем полностью пустой или пустой у бота или заполняемый только ботом ***

                        // определение наиболее подходимой карты для города, пустого у бота, но заявленного ВСЕМИ противником
                        if (towns[t].card.Count == 0 && enemyClaimCount == EnemyTowns.Count)
                        {
                            TownSt[t] = TownType.BotEmptyClmEnemyALL;
                            AI.AddAILog("город пуст у бота и заявлен ВСЕМИ противниками", 2);
                            bool MoreStrong;
                            int cardInd = GetFitCardInNewTownByEnemies(EnemyTowns, t, cards, out MoreStrong);
                            if (cardInd == -1)
                            {
                                AI.AddAILog("Наиболее подходящей карты нет", 2);
                            }
                            else
                            {
                                if (MoreStrong)
                                {
                                    AI.AddAILog("Наиболее подходящая карта (заведомо сильнее): " + cardInd.ToString(), 2);
                                    fits[t, cardInd] = CardFit.FitMoreStrong;
                                }
                                else
                                {
                                    AI.AddAILog("Наиболее подходящая карта: " + cardInd.ToString(), 2);
                                    fits[t, cardInd] = CardFit.Fit;
                                }
                            }
                            continue;
                        }

                        // определение наиболее подходящей карты для города, заявленного только ботом
                        if (towns[t].card.Count > 0 && enemyClaimCount == 0)
                        {
                            AI.AddAILog("город заявлен только ботом", 2);
                            TownSt[t] = TownType.OnlyBot;
                            CardFit fitStat;
                            int cardInd = GetFitCardInOnlyBotTown(towns[t], cards, out fitStat);
                            if (cardInd != -1)
                            {
                                AI.AddAILog("Наиболее подходящая карта (" + fitStat.ToString() + "): " + cardInd.ToString(), 2);
                                fits[t, cardInd] = fitStat;
                            }
                            else AI.AddAILog("Наиболее подходящая карты нет", 2);
                            continue;
                        }

                        // смотрим, не сливной ли город
                        // если город заведомо выигрышный или заведомо проигрышный, делаем его сливным
                        bool bLost = TownAlreadyLost(EnemyTowns, t, towns[t]);
                        bool bWin = TownAlreadyWin(EnemyTowns, t, towns[t]);
                        if (bLost || bWin)
                        {
                            TownSt[t] = TownType.DropTown;
                            AI.AddAILog("город сливной поскольку он заведомо" + (bLost ? " проигрышный" : "") + (bWin ? " выигрышный" : ""), 2);
                            continue;
                        }

                        // определяем подходимость карт для конфликтного города
                        // ПОДХОДИМОСТЬ КАРТЫ (для конфликтного города)
                        // карта делает город более сильным (MoreStrong) - при выкладывании, город будет лучше, чем у каждого их противников
                        // карта подходит достаточно (Fit) - при выкладывании, город будет НЕ хуже, чем у каждого их противников
                        // карта полностью подходит (FitAll) - карта покрывает все накапливамые линии4 для города
                        // карта не подходит (NotFit) - при выкладывании, город будет ХУЖЕ, чем у кого-то их противников (В этом случае смотрится подходимость без учета противника (H2 L2 H1 L1))

                        AI.AddAILog("город конфликтный", 2);
                        TownSt[t] = TownType.FightTown;
                        // смотрим накапливаемые линии4 города
                        bool[] Lines4Troops = towns[t].GetCollectedTroops(4);
                        // пробегаемся по всем картам в руке и смотрим совпадения
                        for (int c = 0; c < cards.Count; c++)
                        {
                            // смотрим подходимость
                            fits[t, c] = TownFit(EnemyTowns, t, towns[t], cards[c]);

                            // подсчет значения подходимости
                            fitVals[t, c] = GetFitVal(towns[t], cards[c].troop);
                            AI.AddAILog("карта " + c.ToString() + " >> " + fits[t, c].ToString() + " (" + fitVals[t, c].ToString() + ")", 2);
                        }

                    }
                }
                #endregion *** определение типов городов и подходимости карт ***

                //A поиск и выбор полностью пустого города
                List<int> AllEmptyInd = new List<int>();
                int AllEmptyTownInd = -1;
                for (int t = 0; t < 5; t++) if (TownSt[t] == TownType.AllEmpty) AllEmptyInd.Add(t);
                if (AllEmptyInd.Count > 0) AllEmptyTownInd = AllEmptyInd[rnd.Next(AllEmptyInd.Count)];

                //*** ШАГИ *********************************************************************************************************

                AI.AddAILog("01 ПОПЫТКА СЛИТЬ ЯВНО-СЛИВНУЮ КАРТУ В ЯВНО-СЛИВНОЙ ГОРОД", 0);
                p = Step_DropCard(towns, cards, TownSt);
                if (p.NumTown != -1) return p;

                AI.AddAILog("02 ВЫКЛАДЫВАНИЕ В ПУСТОЙ ГОРОД МЕГАСИЛЬНОЙ КАРТЫ", 0);
                p = Step_MegaStrongCard(EnemyTowns, towns, cards, TownSt, AllEmptyTownInd);
                if (p.NumTown != -1) return p;

                AI.AddAILog("03 ВЫКЛАДЫВАНИЕ ПОДХОДЯЩЕЙ КАРТЫ К КОНФЛИКТНОМУ ГОРОДУ, ЕСЛИ ОНА ЕСТЬ", 0);
                p = Step_CardInFightTown(towns, cards, TownSt, fits);
                if (p.NumTown != -1) return p;

                AI.AddAILog("04 ВЫКЛАДЫВАНИЕ ПОДХОДЯЩЕЙ КАРТЫ H2 К ГОРОДУ, ЗАЯВЛЕННОМУ ТОЛЬКО БОТОМ", 0);
                p = Step_CardInOnlyBotTown(towns, cards, TownSt, fits, CardFit.H2);
                if (p.NumTown != -1) return p;

                AI.AddAILog("05 КАРТУ В ПУСТОЙ ГОРОД, ЗАЯВЛЕННЫЙ УЖЕ ВСЕМИ ПРОТИВНИКАМИ, ГДЕ КАРТА ЯВНО СИЛЬНЕЕ", 0);
                p = Step_CardInEmptyBotAndAllFillEnemy(cards, TownSt, fits, CardFit.FitMoreStrong);
                if (p.NumTown != -1) return p;

                AI.AddAILog("06.1 ПОПЫТКА СЛИТЬ ЯВНО-СЛИВНУЮ КАРТУ В ВЕРОЯТНО-ПРОИГРЫШНЫЙ ГОРОД", 0);
                p = Step_DropCardInLikelyLoseTown(EnemyTowns, towns, cards, TownSt);
                if (p.NumTown != -1) return p;

                AI.AddAILog("06.2 !ЯВНО-СЛИВНУЮ КАРТУ В ПУСТОЙ ГОРОД, ЗА КОТОРЫЙ БОРЕТСЯ КАК МИНИМУМ 2 ИГРОКА, КОТОРЫЕ ОБА КОПЯТ 2 ПО 2 ЛИНИИ4 МИНИМУМ И С КРАСНЫМИ", 0);
                p = Step_DropInEnemyFightTown(EnemyTowns, towns, cards, TownSt);
                if (p.NumTown != -1) return p;

                AI.AddAILog("07 ВЫКЛАДЫВАНИЕ ПОДХОДЯЩЕЙ КАРТЫ L2 К ГОРОДУ, ЗАЯВЛЕННОМУ ТОЛЬКО БОТОМ", 0);
                p = Step_CardInOnlyBotTown(towns, cards, TownSt, fits, CardFit.L2);
                if (p.NumTown != -1) return p;

                AI.AddAILog("!!! подходящих городов нет. теперь, либо в пустые класть, либо сливать, либо класть в город не подходящую карту", 0);

                AI.AddAILog("10 САМУЮ СИЛЬНУЮ КАРТУ В ПОЛНОСТЬЮ ПУСТОЙ ГОРОД", 0);
                #region *** самую сильную карту в полностью пустой город ***
                if (AllEmptyTownInd != -1)
                {
                    p.NumTown = AllEmptyTownInd;
                    p.NumCardInHand = GetMaxWeightInd(cards, null);
                    AI.AddAILog("карта выложена", 1);
                    return p;
                }
                else AI.AddAILog("полностью пустых городо внет", 1);
                #endregion *** самую сильную карту в полностью пустой город ***

                AI.AddAILog("!!! полностью-пустых городов больше нет", 0);

                AI.AddAILog("11 КАРТУ В ПУСТОЙ ГОРОД, ЗАЯВЛЕННЫЙ УЖЕ ВСЕМИ ПРОТИВНИКАМИ, ГДЕ КАРТА НЕ СЛАБЖЕ", 0);
                p = Step_CardInEmptyBotAndAllFillEnemy(cards, TownSt, fits, CardFit.Fit);
                if (p.NumTown != -1) return p;

                AI.AddAILog("12 САМУЮ СИЛЬНУЮ КАРТУ В ПУСТОЙ ГОРОД", 0);
                p = Step_StrongestCardInEmptyTown(EnemyTowns, cards, TownSt);
                if (p.NumTown != -1) return p;

                AI.AddAILog("!!!пустых городов больше нет", 0);


                if (p.NumTown != -1) return p;

                AI.AddAILog("13 САМУЮ ПОДХОДЯЩУЮ КАРТУ H2 В КОНФЛИКТНЫЙ ГОРОД БЕЗ УЧЕТА КАРТ ПРОТИВНИКА", 0);
                p = Step_MostFitCardWithoutEnemy(EnemyTowns, towns, cards, TownSt, fits, CardFit.H2);
                if (p.NumTown != -1) return p;

                AI.AddAILog("14 ВЫКЛАДЫВАНИЕ ПОДХОДЯЩЕЙ КАРТЫ H1 К ГОРОДУ, ЗАЯВЛЕННОМУ ТОЛЬКО БОТОМ", 0);
                p = Step_CardInOnlyBotTown(towns, cards, TownSt, fits, CardFit.H1);
                if (p.NumTown != -1) return p;

                AI.AddAILog("15 САМУЮ ПОДХОДЯЩУЮ КАРТУ L2 В КОНФЛИКТНЫЙ ГОРОД БЕЗ УЧЕТА КАРТ ПРОТИВНИКА", 0);
                p = Step_MostFitCardWithoutEnemy(EnemyTowns, towns, cards, TownSt, fits, CardFit.L2);

                AI.AddAILog("16 САМУЮ ПОДХОДЯЩУЮ КАРТУ H1 В КОНФЛИКТНЫЙ ГОРОД БЕЗ УЧЕТА КАРТ ПРОТИВНИКА", 0);
                p = Step_MostFitCardWithoutEnemy(EnemyTowns, towns, cards, TownSt, fits, CardFit.H1);
                if (p.NumTown != -1) return p;

                AI.AddAILog("17 ВЫКЛАДЫВАНИЕ ПОДХОДЯЩЕЙ КАРТЫ L1 К ГОРОДУ, ЗАЯВЛЕННОМУ ТОЛЬКО БОТОМ", 0);
                p = Step_CardInOnlyBotTown(towns, cards, TownSt, fits, CardFit.L1);
                if (p.NumTown != -1) return p;

                AI.AddAILog("18 САМУЮ ПОДХОДЯЩУЮ КАРТУ", 0);
                return Step_MostFitCard(EnemyTowns, towns, cards, TownSt);

            }
            catch (Exception ex)
            {
                AI.AddAILog("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0);
                AI.AddAILog("!!!!!!!!!!ОШИБКА: " + ex.Message, 0);
                AI.AddAILog("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0);
                return BotFirst.PlayCard(towns, cards);
            }
        }

    }
}
